package appTool

import (
	"bufio"
	"context"
	"errors"
	"fmt"
	"gitee.com/kinwyb/appTools/common/exit"
	"gitee.com/kinwyb/appTools/log"
	"gitee.com/kinwyb/godb"
	"github.com/sirupsen/logrus"
	"github.com/spf13/cobra"
	"github.com/spf13/pflag"
	"go.etcd.io/etcd/client/v3"
	"net"
	"os"
	"runtime/debug"
	"strings"
	"time"
)

type closeFun = func()

// RpcClientFunc rpc客户端函数
var RpcClientFunc func() []RpcxClient

type Application struct {
	cmd                      *cobra.Command
	Context                  context.Context //程序执行上下文
	ctxCancel                context.CancelFunc
	ConfigReader             ConfigReader              `description:"配置读取对象"`
	ConfigReaderInitCallback func(reader ConfigReader) `description:"配置读取对象加载完成后回调"`
	trace                    *appTrace                 `description:"应用追踪"`
	http                     *RestfulHttp              `description:"应用Http服务"`
	rpcx                     *AppRpcx                  `description:"rpcx服务"`
	prepareFunc              func(app *Application)    `description:"预处理函数"`
	afterFunc                func(app *Application)    `description:"启动后续处理"`
	runFunc                  func(ctx context.Context) `description:"执行函数"`
	closeFuns                []closeFun
	cfg                      appCfg
	ver                      *appVer
	gracefulReloadCmd        *appGracefulReload
	isRun                    bool
	broadcast                *etcdBroadcast
	httpListenFile           *os.File
	rpcListenFile            *os.File
	gracefulReload           bool
	doGracefulReload         bool
	statusMsg                string //状态内容
	unixSocket               net.Listener
	parsedFlag               bool //已解析命令参数
}

func NewApplication(appName string) *Application {
	GloablApp := &Application{}
	GloablApp.ver = newAppVerCommand()
	GloablApp.SetAppName(appName)
	GloablApp.gracefulReloadCmd = newAppGracefulReload()
	GloablApp.gracefulReloadCmd.app = GloablApp
	GloablApp.cfg.cfg = getDefaultGloablConfig()
	exit.Listen(func(args ...interface{}) {
		GloablApp.Close()
	})
	logrus.RegisterExitHandler(exit.Exit)
	return GloablApp
}

// AppConfig 获取应用配置
func (a *Application) AppConfig() AppConfig {
	return a.cfg.cfg
}

func (a *Application) Command() *cobra.Command {
	a.initCmd()
	return a.cmd
}

func (a *Application) SetConfig(cfg AppConfig) *Application {
	a.cfg.cfg = cfg
	return a
}

// SetAppName 设置应用名称
func (a *Application) SetAppName(name string) *Application {
	a.cfg.appName = name
	if a.cfg.appShort == "" {
		a.cfg.appShort = name
	}
	if a.cfg.appDescription == "" {
		a.cfg.appDescription = name
	}
	return a
}

// SetLogFileName 设置日志输出文件名(elasticsearch index name)
func (a *Application) SetLogFileName(logFileName string) *Application {
	a.cfg.logFileName = logFileName
	return a
}

// EnableApollo 开启apollo,参数数组第一位表示apollo应用,第二位表示apollo地址，如果为空
// 通过cmd flag解析取值
func (a *Application) EnableApollo(apollo ...string) *Application {
	a.cfg.enableApolloCfg = true
	flag := a.Flag()
	if len(apollo) > 0 && apollo[0] != "" {
		a.cfg.apolloApp = apollo[0]
	} else {
		apolloAppEnv := strings.ToUpper(a.cfg.appName + "_APOLLO_APP")
		apolloAppEnv = strings.ReplaceAll(apolloAppEnv, "-", "_")
		defaultApolloApp := strings.TrimSpace(os.Getenv(apolloAppEnv))
		if defaultApolloApp == "" {
			defaultApolloApp = a.cfg.appName
		}
		flag.StringVar(&a.cfg.apolloApp, "apollo_app", defaultApolloApp, "apollo应用ID,环境变量["+apolloAppEnv+"]")
	}
	if len(apollo) > 1 && apollo[1] != "" {
		a.cfg.apolloURL = apollo[1]
	} else {
		apolloUrlEnv := strings.ToUpper(a.cfg.appName + "_APOLLO_URL")
		apolloUrlEnv = strings.ReplaceAll(apolloUrlEnv, "-", "_")
		defailtApolloURL := strings.TrimSpace(os.Getenv(apolloUrlEnv))
		if defailtApolloURL == "" {
			defailtApolloURL = strings.TrimSpace(os.Getenv("APOLLO_URL"))
		}
		flag.StringVar(&a.cfg.apolloURL, "apollo", defailtApolloURL, "apollo服务地址,环境变量["+apolloUrlEnv+" 或 APOLLO_URL]")
	}
	return a
}

// LoadFileConfig 加载文件配置
func (a *Application) LoadFileConfig(filePath ...string) *Application {
	a.cfg.enableFileCfg = true
	flag := a.Flag()
	defaultConfigFilePath := "config.json"
	if len(filePath) > 0 && filePath[0] != "" {
		defaultConfigFilePath = filePath[0]
	}
	configPath := ""
	flag.StringVar(&configPath, "config", defaultConfigFilePath, "配置文件地址,环境变量["+defaultConfigFilePath+"]")
	configPath = strings.TrimSpace(configPath)
	if configPath == "" {
		configFilePathEnv := strings.ToUpper(a.cfg.appName + "_CONFIG")
		configFilePathEnv = strings.ReplaceAll(configFilePathEnv, "-", "_")
		configPath = strings.TrimSpace(os.Getenv(configFilePathEnv))
		if configPath == "" {
			configPath = defaultConfigFilePath
		}
	}
	a.cfg.configFilePath = configPath
	return a
}

// LoadConfigData 加载配置文件内容
func (a *Application) LoadConfigData(data []byte) *Application {
	a.cfg.enableFileCfg = true
	a.cfg.configFileData = data
	return a
}

// SetAppShort 设置应用简称
func (a *Application) SetAppShort(appShort string) *Application {
	a.cfg.appShort = appShort
	return a
}

// SetAppDescription 设置应用描述
func (a *Application) SetAppDescription(desc string) *Application {
	a.cfg.appDescription = desc
	return a
}

// PrepareFunc 设置预处理函数
func (a *Application) PrepareFunc(fun func(application *Application)) *Application {
	a.prepareFunc = fun
	return a
}

// AfterFunc 设置Run执行后函数
func (a *Application) AfterFunc(fun func(application *Application)) *Application {
	a.afterFunc = fun
	return a
}

// RunFunc 设置运行函数
func (a *Application) RunFunc(fun func(ctx context.Context)) *Application {
	a.runFunc = fun
	return a
}

// SetVersion 设置版本号
func (a *Application) SetVersion(version string) *Application {
	a.ver.version = version
	return a
}

// EnableHttp 开启http服务
func (a *Application) EnableHttp(addr string, port int) *Application {
	a.cfg.httpAddr = addr
	a.cfg.httpPort = port
	if a.http == nil {
		a.http = &RestfulHttp{}
	}
	a.http.listenAddr = addr
	a.http.port = port
	return a
}

// DisableHttp 禁用http服务
func (a *Application) DisableHttp() *Application {
	if a.http != nil {
		a.http.Shutdown(context.Background())
	}
	a.http = nil
	return a
}

// Http 获取http服务对象
func (a *Application) Http() *RestfulHttp {
	return a.http
}

// Rpcx 获取rpc服务对象
func (a *Application) Rpcx() *AppRpcx {
	return a.rpcx
}

// Flag 解析命令参数
func (a *Application) Flag() *pflag.FlagSet {
	a.initCmd()
	return a.cmd.Flags()
}

// FlagParse 解析命令参数
func (a *Application) FlagParse(p *string, name, defaultVal, usage string) {
	flag := a.Flag()
	valEnv := strings.ToUpper(a.cfg.appName + "_" + name)
	valEnv = strings.ReplaceAll(valEnv, "-", "_")
	envVal := strings.TrimSpace(os.Getenv(valEnv))
	if envVal == "" {
		envVal = defaultVal
	}
	flag.StringVar(p, name, envVal, usage)
}

// FlagParseRpcxServerAddr 解析rpc服务地址参数
func (a *Application) FlagParseRpcxServerAddr() {
	flag := a.Flag()
	rpcxAddrEnv := strings.ToUpper(a.cfg.appName + "_RPCX_ADDR")
	rpcxAddrEnv = strings.ReplaceAll(rpcxAddrEnv, "-", "_")
	defaultRpcxAddr := strings.TrimSpace(os.Getenv(rpcxAddrEnv))
	if defaultRpcxAddr == "" {
		defaultRpcxAddr = strings.TrimSpace(os.Getenv("RPCX_ADDR"))
		if defaultRpcxAddr == "" {
			defaultRpcxAddr = "tcp@127.0.0.1:10001"
		}
	}
	flag.StringVarP(&a.cfg.rpcxServerAddr, "rpcx_server_addr", "r", defaultRpcxAddr, "rpcx服务地址,环境变量["+rpcxAddrEnv+"]")
	a.cfg.enableRpcxServer = true
}

func (a *Application) SetRpcxServerAddr(addr string) *Application {
	if strings.TrimSpace(addr) != "" {
		a.cfg.rpcxServerAddr = addr
		a.cfg.enableRpcxServer = true
	}
	return a
}

// FlagParseArgs 解析参数，默认解析运行参数
func (a *Application) FlagParseArgs(args ...string) {
	if len(args) < 1 {
		args = os.Args[1:]
	}
	a.Flag().Parse(args)
	a.parsedFlag = true
}

// EnableRpcx 开启rpcx服务
func (a *Application) EnableRpcx() *AppRpcx {
	if a.rpcx == nil {
		a.rpcx = &AppRpcx{
			serverMode: a.cfg.cfg.ServerMode(),
		}
	}
	return a.rpcx
}

// InitRpcxClient 开启rpcx服务客户端
func (a *Application) InitRpcxClient(client ...RpcxClient) *Application {
	a.cfg.rpcxClients = client
	a.cfg.rpcxClientEnable = true
	return a
}

func (a *Application) SetRpcxServer(service ...RpcxService) *Application {
	a.cfg.rpcxService = append(a.cfg.rpcxService, service...)
	return a
}

func (a *Application) ListenBroadcast(fun BroadcastMessageCallback) {
	if a.broadcast == nil {
		a.broadcast = newEtcdBroadcast()
	}
	a.broadcast.AddCallback(fun)
	if a.isRun {
		a.broadcast.SetEtcdHosts(a.cfg.cfg.EtcdHosts())
		a.broadcast.SetBasePath(a.cfg.cfg.BroadcastPath())
		a.broadcast.Start(a.Context)
	}
}

// IgnoreConfigCheck 忽略配置检测
func (a *Application) IgnoreConfigCheck() {
	a.cfg.ignoreConfig = true
}

// BroadcastMessage 广播消息
func (a *Application) BroadcastMessage(msgType uint8, msg []byte) error {
	if a.broadcast == nil {
		return errors.New("广播尚未初始化")
	}
	return a.broadcast.SendMessage(msgType, msg)
}

func (a *Application) initCmd() {
	if a.cmd == nil {
		a.cmd = &cobra.Command{
			Run: a.cmdRun,
		}
		a.cmd.AddCommand(a.ver.cmd)
		a.cmd.AddCommand(a.gracefulReloadCmd.cmd)
		log.SetLogFlag(a.cmd.Flags())
		a.cmd.Flags().BoolVar(&a.gracefulReload, "graceful", false, "热重启")
	}
}

func (a *Application) EnableUnixSocket() *Application {
	a.doGracefulReload = true
	return a
}

func (a *Application) loadConfig() {
	cfg := a.cfg.cfg
	if cfg == nil {
		return
	}
	if a.cfg.tracerServerURL == "" && cfg.TraceServerURL() != "" {
		a.cfg.tracerServerURL = cfg.TraceServerURL()
	}
	pprof := cfg.PprofHttpPort()
	if pprof > 0 {
		if a.http == nil {
			a.EnableHttp("", pprof)
		}
		a.http.EnablePprof()
	}
	if a.rpcx == nil {
		if a.cfg.rpcxClientEnable || a.cfg.enableRpcxServer || len(a.cfg.rpcxService) > 0 {
			a.EnableRpcx()
		}
	}
	if a.rpcx != nil {
		etcds := cfg.EtcdHosts()
		if len(etcds) > 0 && cfg.RpcBasePath() != "" {
			a.rpcx.etcds = etcds
			a.rpcx.basePath = cfg.RpcBasePath()
			a.rpcx.serverMode = a.cfg.cfg.ServerMode()
			etcd, err := clientv3.New(clientv3.Config{Endpoints: etcds})
			if err != nil {
				panic("rpcx etcd连接失败:" + err.Error())
			}
			a.rpcx.etcdClient = etcd
		} else if a.rpcx.registerPlugin == nil || a.rpcx.clientDiscovery == nil {
			panic("rpcx 默认依赖etcd注册中心请配置etcd地址或者指定注册中心插件")
		}
		if a.cfg.rpcxClientEnable {
			clients := a.cfg.rpcxClients
			if RpcClientFunc != nil {
				clients = append(clients, RpcClientFunc()...)
			}
			a.rpcx.Client(clients...)
		}
		if a.cfg.enableRpcxServer {
			if a.cfg.rpcxServerAddr == "" {
				panic("请配置rpcx服务地址")
			} else {
				a.rpcx.EnableServer(a.cfg.rpcxServerAddr)
				for _, service := range a.cfg.rpcxService {
					_ = a.rpcx.RegisterService(service)
				}
			}
		}
	}
	cfg.LoadFinishCallback()
}

// 执行代码
func (a *Application) cmdRun(cmd *cobra.Command, args []string) {
	defer func() {
		if err := recover(); err != nil {
			log.AppTool.WithField("info", string(debug.Stack())).Errorf("Panic: %v", err)
			a.Close()
			log.AppTool.Panic(err)
		}
	}()
	if !a.parsedFlag {
		a.FlagParseArgs()
	}
	a.statusMsg = "系统开始启动"
	a.isRun = true
	if a.gracefulReload { //热重启
		a.httpListenFile = os.NewFile(3, "")
		a.rpcListenFile = os.NewFile(4, "")
	}
	a.Context, a.ctxCancel = context.WithCancel(context.Background())
	if a.doGracefulReload {
		a.doGracefulReload = false
		go a.startUnixSocket(a.Context)
	}
	a.statusMsg = "加载配置中..."
	if a.cfg.enableApolloCfg {
		if a.cfg.apolloURL == "" {
			panic("请配置apollo地址")
		}
		apolloCfg, err := NewApollo(a.cfg.apolloURL, a.cfg.apolloApp, "common")
		if err != nil {
			panic("配置中心[" + a.cfg.apolloURL + ":" + a.cfg.apolloApp + "]连接失败:" + err.Error())
		}
		a.ConfigReader = apolloCfg
	} else if a.cfg.enableFileCfg {
		if a.cfg.configFilePath == "" && len(a.cfg.configFileData) < 1 {
			panic("请填写配置文件地址或文件内容")
		}
		if len(a.cfg.configFileData) > 0 {
			cfg, err := LoadFileConfigByData(a.cfg.configFileData)
			if err != nil {
				panic("配置文件内容加载失败:" + err.Error())
			}
			a.ConfigReader = cfg
		} else {
			cfg, err := LoadFileConfig(a.cfg.configFilePath)
			if err != nil {
				panic("配置文件加载失败:" + err.Error())
			}
			a.ConfigReader = cfg
		}
	} else if !a.cfg.ignoreConfig {
		reader := bufio.NewReader(os.Stdin)
		for {
			fmt.Print("未加载任何配置信息是否继续(Y|N) -> ")
			text, _ := reader.ReadString('\n')
			// convert CRLF to LF
			text = strings.Replace(text, "\n", "", -1)
			if strings.Compare("N", strings.ToUpper(text)) == 0 {
				fmt.Println("未加载任何配置应用退出")
				return
			} else if strings.Compare("Y", strings.ToUpper(text)) == 0 {
				break
			} else {
				fmt.Println("请输入有效值(Y|N)")
			}
		}
	}
	if a.ConfigReader != nil {
		if cfg, ok := a.cfg.cfg.(AppConfigInit); ok {
			cfg.Init(a.ConfigReader)
		}
	}
	a.watchPprofApolloConfig()
	if a.ConfigReader != nil && a.ConfigReaderInitCallback != nil {
		a.ConfigReaderInitCallback(a.ConfigReader)
	}
	if a.cfg.logFileName == "" {
		a.cfg.logFileName = a.cfg.appName
	}
	log.InitLog(a.cfg.cfg.LogLevel(), a.cfg.cfg.LogToFile(), a.cfg.logFileName)
	log.SetLogEnv(a.cfg.cfg.RunMode().String(), a.AppName())
	godb.SetLog(log.Tag("db"))
	if a.prepareFunc != nil {
		a.prepareFunc(a)
	}
	a.loadConfig()
	a.statusMsg = "启动服务中..."
	if a.cfg.tracerServerURL != "" {
		a.trace = &appTrace{
			cfg: &a.cfg,
		}
		a.trace.initTracing()
		defer a.trace.closeTracing()
	}
	if a.broadcast != nil {
		a.broadcast.SetBasePath(a.cfg.cfg.BroadcastPath())
		a.broadcast.SetEtcdHosts(a.cfg.cfg.EtcdHosts())
		err := a.broadcast.Start(a.Context)
		if err != nil {
			log.AppTool.WithError(err).Error("广播模块启动失败")
		}
	}
	//启动http服务
	if a.http != nil {
		a.http.listenFile = a.httpListenFile
		go a.http.Run()
	}
	//启动rpc服务
	if a.rpcx != nil {
		a.rpcx.listenFile = a.rpcListenFile
		a.rpcx.Run()
	}
	if a.afterFunc != nil {
		a.afterFunc(a)
	}
	a.statusMsg = "系统启动完成"
	if a.runFunc == nil {
		<-a.Context.Done()
	} else {
		a.runFunc(a.Context)
	}
	a.Close()
}

func (a *Application) Close() {
	if !a.isRun {
		return
	}
	a.isRun = false
	if a.broadcast != nil {
		a.broadcast.Close()
	}
	// 函数运行结束处理
	for _, v := range a.closeFuns {
		v()
	}
	if a.http != nil {
		shutdownCtx, _ := context.WithTimeout(context.Background(), 30*time.Second)
		a.http.Shutdown(shutdownCtx)
	}
	if a.rpcx != nil {
		shutdownCtx, _ := context.WithTimeout(context.Background(), 30*time.Second)
		a.rpcx.Shutdown(shutdownCtx)
	}
	if a.unixSocket != nil {
		a.unixSocket.Close()
	}
	// 最后关闭上下文原因：是关闭上下文后主程序很可能直接执行完成导致整个程序的退出，从而没有时间来关闭上面这些资源
	// 所以在关闭上面资源后再关闭上下文可以保证资源被准确的关闭
	if a.ctxCancel != nil {
		a.ctxCancel()
	}
}

func (a *Application) Run() error {
	a.initCmd()
	a.cmd.Use = a.cfg.appName
	a.cmd.Short = a.cfg.appShort
	a.cmd.Long = a.cfg.appDescription
	return a.cmd.Execute()
}

func (a *Application) watchPprofApolloConfig() {
	if a.ConfigReader == nil {
		return
	}
	a.ConfigReader.WatchKey(pprofPortConfigKey).SetInt64Callback(func(pprof int64) {
		appHttp := a.Http()
		if appHttp != nil {
			if appHttp.port == int(pprof) {
				appHttp.EnablePprof()
				return
			}
			// 端口变动,先关闭之前监听的端口
			a.DisableHttp()
		}
		// 重新开启端口
		if pprof > 0 {
			go a.EnableHttp("", int(pprof)).http.EnablePprof().Run()
		}
	})
}

// RunTest 测试运行，不解析flag
//
// 执行会在应用初始完成后返回,不和Run()函数一样会阻塞直到程序结束
// 其主要目的是初始化整个应用加载相关配置数据
// 如有需要请定义运行函数RunFunc
func (a *Application) RunTest() {
	if a.runFunc == nil {
		// 当没有运行函数时,默认一个运行函数执行防止一直阻塞.
		// 主要用于测试时初始化应用数据以便后续测试代码可以正常执行
		a.runFunc = func(ctx context.Context) {
			log.AppTool.Info("Application run Test")
		}
	}
	a.cmdRun(nil, nil)
}

func (a *Application) RegCloseFun(fun closeFun) *Application {
	a.closeFuns = append(a.closeFuns, fun)
	return a
}

// AppName 应用名称
func (a *Application) AppName() string {
	return a.cfg.appName
}
